/**
 *  EKology - An Ecosystem Simulation Framework
 *  Copyright (C) 2008  Aarón Tavío - aaron.tavio at gmail.com - ULPGC
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 */

package net.ekology.core.datatypes;

import net.ekology.ekosystem.datatypes.EKEvent;

import java.util.Hashtable;
import java.util.Vector;
import java.io.Serializable;


/**
 * An implementation of a final state machine
 * <p>
 * Agent's behaviour is determined, in part, by the <code>Automaton</code>
 * implemented in their DNAs
 * 
 * @author Aarón Tavío - aaron.tavio at gmail.com
 * @version 1.0.0 - 20081019-1610
 */
public class Automaton implements Serializable {
    private String sCurrentState;
    private String sInitialState;
    private Hashtable<String,State> htState;
    private Hashtable<String,Hashtable<String,TransitionRule>> htTransitionRule;
    
    
    // Methods
    public Automaton() {
        this.sCurrentState = null;
        this.sInitialState = null;
        this.htState = new Hashtable<String,State>();
        this.htTransitionRule = new Hashtable<String,Hashtable<String,TransitionRule>>();
    }
    
    
    public Automaton(String sCurrentState, String sInitialState, Hashtable<String,State> htState, Hashtable<String,Hashtable<String,TransitionRule>> htTransitionRule) {
        this.sCurrentState = sCurrentState;
        this.sInitialState = sInitialState;
        this.htState = htState;
        this.htTransitionRule = htTransitionRule;        
    }
    
    
    public InitialState getInitialState() {
        return (InitialState) getState(this.sInitialState);
    }
    
    
    public void setInitialState(String sInitialState) {
        if (this.sInitialState == null && getState(sInitialState) != null) this.sInitialState = sInitialState;
    }
    
    
    public FinalState[] getFinalStates() {
        FinalState[] aResult = null;
        Vector<FinalState> vFinalState = new Vector<FinalState>();
        
        for(State oState: htState.values())
            if (oState instanceof FinalState) vFinalState.add((FinalState) oState);

        aResult = new FinalState[vFinalState.size()];
            
        return vFinalState.toArray(aResult);
    }

    
    public String getCurrentStateName() {
        return this.sCurrentState;
    }
    
    
    public void setCurrentStateName(String sCurrentState) {
        if (this.sCurrentState == null && getState(sCurrentState) != null) this.sCurrentState = sCurrentState;
    }
    
    
    public State getCurrentState() {
        return getState(this.sCurrentState);
    }
    
    
    public State getState(String sName) {
        return htState.get(sName);
    }
    
    
    public void addState(State oState) {
        if (htState.get(oState.getName()) == null) this.htState.put(oState.getName(), oState);
    }
    
    
    public int getStateCount() {
        return htState.size();
    }
    
    
    public void removeState(String sName) {
        htState.remove(sName);
    }
    
        
    public TransitionRule getTransitionRule(String sSourceState, String sFiringEvent) {
        TransitionRule oResult = null;
        Hashtable<String,TransitionRule> htTRule;
                
        htTRule = this.htTransitionRule.get(sSourceState);
        if (htTRule != null) oResult = htTRule.get(sFiringEvent);
        
        return oResult;
    }
    
    
    public void addTransitionRule(TransitionRule oTransitionRule) {
        Hashtable<String,TransitionRule> htStateTransition,htNewRule;
                
        htStateTransition = htTransitionRule.get(oTransitionRule.getSourceState());
        if (htStateTransition != null) {
            if (htStateTransition.get(oTransitionRule.getFiringEvent()) == null) htStateTransition.put(oTransitionRule.getFiringEvent(), oTransitionRule);
        }
        else {
            htNewRule = new Hashtable<String,TransitionRule>();    
            htNewRule.put(oTransitionRule.getFiringEvent(),oTransitionRule);
            htTransitionRule.put(oTransitionRule.getSourceState(), htNewRule);
        }
    }
    
    
    public int getTransitionRuleCount() {
        int iResult = 0;
        
        for(Hashtable<String,TransitionRule> htStateTransition: this.htTransitionRule.values()) iResult += htStateTransition.size();
        
        return iResult;
    }
    
    
    public void removeTransitionRule(TransitionRule oTransitionRule) {
        Hashtable<String,TransitionRule> htStateRule;
        
        htStateRule = htTransitionRule.get(oTransitionRule.getSourceState());
        if (htStateRule != null) htStateRule.remove(oTransitionRule.getFiringEvent());
    }
    
    
    /**
     * Gathers events from the ecosystem and creates automaton events
     * <p>
     * <code>DO</code>, <code>ENTRY</code> or <code>EXIT</code> events argenerated as the automaton goes through transition rules
     * 
     * @param vEKEvent vector containing EKEvents
     * @return generated automaton events
     */
    public AutomatonEvent[] step(Vector<EKEvent> vEKEvent) {
        AutomatonEvent[] aAutomatonEvent = null;
        TransitionRule oTransitionRule = null;
        
        if (vEKEvent != null) {
            for(EKEvent oFiringEvent: vEKEvent) {
                oTransitionRule = getTransitionRule(sCurrentState,oFiringEvent.getName());
                if (oTransitionRule != null) {

                    aAutomatonEvent = new AutomatonEvent[2];
                    aAutomatonEvent[0] = new AutomatonEvent(oTransitionRule.getSourceState(), AutomatonEventType.EXIT, oFiringEvent);
                    aAutomatonEvent[1] = new AutomatonEvent(oTransitionRule.getTargetState(), AutomatonEventType.ENTRY, oFiringEvent);

                    sCurrentState = oTransitionRule.getTargetState();
                }
                break;
            }
        }
        
        if (aAutomatonEvent == null) { // No transition
            aAutomatonEvent = new AutomatonEvent[1];
            aAutomatonEvent[0] = new AutomatonEvent(sCurrentState, AutomatonEventType.DO, null);
        }        
        
        return aAutomatonEvent;
    }

}